/* vim:ft=objc:
 * $Id: LineIO.m 154 2006-08-20 18:51:20Z brandenm $
 *
 * Copyright (c) 2004, 2005  Branden J. Moore.
 *
 * This file is part of MacBiff, and is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * MacBiff is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with MacBiff; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA  02111-1307 USA.
 *
 */

#import "LineIO.h"
#include "ssl.h"
#include <errno.h>

#include "debug.h"

@implementation LineIO

- (id) initWithHost: (NSString*) h andPort: (int) p
{
	if ( ( self = [super init] ) != Nil ) {
		port = p;
		host = [[NSString alloc] initWithString: h];
		ssl = Nil;
		conn = Nil;
		buffer = [[NSMutableString alloc] initWithString: @""];
	}
	return self;
}


- (void) dealloc
{
	if ( buffer ) [buffer release];
	if ( host ) [host release];
	if ( ssl ) {
		SSL_shutdown( ssl );
		SSL_free( ssl );
	} else if ( conn ) {
		BIO_free( conn );
	}

	[super dealloc];
}


- (int) connectWithSSL: (BOOL) use_SSL
{
	char connStr[512];

	if ( conn ) {
		alert("Already connected.  Let's Disconnect!\n");
		[self disconnect];
	}

	memset(connStr, 0, 512);
	snprintf(connStr, 511, "%s:%d", [host cString], port);
	dprintf("Connecting to %s...\n", connStr);

	if ( !(conn = BIO_new_connect( connStr ) ) ) {
		alert("Error creating BIO Connection!\n");
		ERR_print_errors_fp( _dbgfp );
		return 1;
	}

	if ( BIO_do_connect( conn ) <= 0 ) {
		alert("Error connecting to Server!\n");
		ERR_print_errors_fp( _dbgfp );
		return 1;
	}

	if ( use_SSL ) {
		ssl = get_new_SSL();
		if ( !ssl ) {
			alert("Unable to get SSL connect!\n");
			return 1;
		}
		SSL_set_bio( ssl, conn, conn );
		if ( SSL_connect( ssl ) <= 0 ) {
			alert("Error establishing SSL with BIO\n");
			ERR_print_errors_fp( _dbgfp );
			return 1;
		}
	}

	return 0;
}


- (void) disconnect
{
	if ( ssl ) {
		SSL_shutdown( ssl );
		SSL_free( ssl );
	} else if ( conn ) {
		BIO_free( conn );
	}
	conn = Nil;
	ssl = Nil;
}


/* Calls disconnect on errors */
- (int) writeLine: (NSString*) line
{
	int res;
	BOOL retry = NO;

	if ( !conn ) return (-1*ENOTCONN);

	do {
		retry = NO;
		if ( ssl ) {
			res = SSL_write( ssl, [line cString], [line length] );
			switch ( SSL_get_error( ssl, res ) ) {
			case SSL_ERROR_NONE:
				break;
			case SSL_ERROR_ZERO_RETURN:
				/* connection closed */
				alert("Connection closed\n");
				[self disconnect];
				break;
			case SSL_ERROR_WANT_READ:
			case SSL_ERROR_WANT_WRITE:
				retry = YES;
				break;
			default:
				/* Some other error */
				alert("SSL_write returned error %lu\n",
						ERR_get_error() );
				[self disconnect];
			}
		} else {
			res = BIO_write( conn, [line cString], [line length] );
			if ( res <= 0 ) {
				if ( BIO_should_retry( conn ) ) {
					retry = YES;
				} else if ( res != 0 ) {
					alert("BIO_write returned error %lu\n",
							ERR_get_error() );
					[self disconnect];
				} else {
					/* Connection closed */
					alert("Connection closed\n");
					[self disconnect];
				}
			}
		}
	} while ( retry );

	return res;
}


/* Calls disconnect on errors */
- (int) read
{
#define BUF_LEN 1024
	char buf[BUF_LEN];
	int res;
	BOOL retry;

	memset( buf, 0, BUF_LEN );

	do {
		retry = NO;
		if ( ssl ) {
			res = SSL_read( ssl, buf, BUF_LEN-1 );
			switch ( SSL_get_error( ssl, res ) ) {
			case SSL_ERROR_NONE:
				break;
			case SSL_ERROR_ZERO_RETURN:
				/* connection closed */
				alert("Connection closed\n");
				[self disconnect];
				res = -1;
				break;
			case SSL_ERROR_WANT_READ:
			case SSL_ERROR_WANT_WRITE:
				retry = YES;
				break;
			default:
				/* Some other error */
				alert("SSL_read returned error %lu (%u)\n",
						ERR_get_error(), errno );
				[self disconnect];
			}
		} else {
			res = BIO_read( conn, buf, BUF_LEN-1 );
			if ( res <= 0 ) {
				if ( BIO_should_retry( conn ) ) {
					retry = YES;
				} else if ( res != 0 ) {
					alert("BIO_read returned error %lu (%u)\n",
							ERR_get_error(), errno );
					[self disconnect];
					res = -1*ENOTCONN;
				} else { /* res == 0 */
					/* Connection closed */
					dprintf("Connection closed\n");
					[self disconnect];
					res = -1*ENOTCONN;
				}
			}
		}
	} while ( retry );


	if ( res == 0 ) {
		res = -1;
	} else if ( res > 0 ) {
		[buffer appendString: [NSString stringWithCString: buf
							   length: res]];
		res = 0;
	}

	return res;
}


- (NSString*) readLine
{
	NSString *line = Nil;
	NSRange newlineRange, lineRange;
	int res;

	if ( !conn ) {
		errno = ENOTCONN;
		return Nil;
	}
	do {
		newlineRange = [buffer rangeOfString: @"\r\n"];
		if ( newlineRange.location == NSNotFound ) {
			newlineRange = [buffer rangeOfString: @"\n"];
		}
		if ( newlineRange.location == NSNotFound ) {
			if ( ( res = [self read] ) != 0 ) {
				/* Had an error */
				alert("Read returned %d\n", res);
				return Nil;
			}
		}
	} while ( newlineRange.location == NSNotFound );

	lineRange.location = 0;
	lineRange.length = newlineRange.location + newlineRange.length;

	line = [[buffer substringWithRange: lineRange] copy];
	[buffer deleteCharactersInRange: lineRange];

	return [line autorelease];
}

@end
